﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using System.Diagnostics;
using System.Linq.Expressions;


namespace IlGenUtil
{
	class Program
	{
		static void Test1()
		{
			// TODO: Invoke медленно работает, из DynamcMethod можно сделать делегата


			Console.WriteLine("\nTest1");

			// Первым примером будет функция, которая получает строчку str и число i.
			// Если i < длины строки, то функция возвращает i-й символ в строке, иначе -- возвращает символ '#'.
			// Изврантый способ реализации призван показать различные возможности библиотеки.
			
			// Создаём типизированную обёртку вокруг DynamicMethod и ILGenerator
			var method = Method.New<String, int, char>("test1");
			// <String, int, char> -- говорит о том, что создаётся метод с двумя аргументами (String, int),
			// возвращающий значение типа char.
			// Другие типы функций задаются так:
			//   - void F() -- Method.New (или Method.New<Nil,Nil>)
			//   - int F() -- Method.New<int> (или Method.New<Nil,int>)
			//   - void F(int) -- Method.New<int,Nil>    -- NB! вместо void пишется Nil
			//   - int F(char) -- Method.New<char,int>
			//   - int F(char, bool) -- Method.New<char,bool,int>
			//   - и т. д.
			// Последний тИповый аргумент указывает тип возвращаемого значения.	


			// Нам понадобится метод, возвращающий i-й символ строки:
			var getChar = Type<String>.VirtualMethod<int,char>("get_Chars");
			// Здесь:
			//   - Type<String> -- получаем метод класса String
			//   - VirtualMethod<int, char> -- виртуальный метод с одним аргументом типа int, возвращающий значение типа char
			// Тип методов задаётся так же как и для Method.New (вначале типы аргументов, затем тип возвращаемого значения).
			// Проверка, что метод действительно виртуальный делается во время выполнения.
			// Статически делается проверка, что на стеке есть необходимые аргументы (включая неявный this).

			// Ещё нужен метод, возвращающий длину сторки:
			var getLength = Type<String>.VirtualMethod<int>("get_Length");

			var g = method.IlGen;

			// Локальная переменая типа int, в которой будем хранить второй аргумент
			// (просто, чтобы показать как работают локальные переменные).
			var loc = g.DeclareLocal<int>();

			var outOfRange = g.DefineLabel();

			// Генерим код:
			var g1 = g
				.Ldarg(N._1)     // сохраним значение второго аргумента
				.Stloc(loc)      // в переменной loc (если на стеке будет лежать не int, то код не скомпилируется)
				.Ldarg(N._0)
				.Dup()           // сохраним строчку на стеке, она нам понадобится
				.Call(getLength) // если положить не тот аргумент или забыть его вовсе, то код не скомпилируется
				.Ldloc(loc)
				.Sub()           // если на стеке нет двух чисел, то IntelliSense даже не предложит этот метод
				.Ldc(0)
				.Ble(outOfRange);
				
			// возвращаем i-й символ
			g1	.Ldarg(N._1)     // строчка уже лежит на стеке, кладём индекс
				.Call(getChar)
				.Ret();          // тип возвращаемого значения проверяется 

			// возвращаем '#'
			g1  .MarkLabel(outOfRange)
				.Pop()           // если забыть пред выходом очистить стек от мусора, то код не скомпилируется
				.Ldc('#')        // Ldc сделан полиморфным, поэтому на стеке будет лежать char (если положить число, то код не скомпилируется)
				.Ret();

			// при вызове метода проверяются типы аргументов + у Invoke конкретный тип результата, а не object
			Console.WriteLine(method.Invoke("abcdefgh", 3 ));
			Console.WriteLine(method.Invoke("abcdefgh", 33 ));
		}


		static void Test2()
		{
			Console.WriteLine("\nTest2");
			var method = Method.New<int>("test2");
			var g = method.IlGen;
			var g1 = g.Ldc(0);
			foreach (var x in new int[] { 1,2,3,4,5,6,7,8,9 })
			{
				g1 = g1.Ldc(x).Add();
			}
			g1.Ret();

			Console.WriteLine(method.Invoke());
		}


		static void Test3()
		{
			Console.WriteLine("\nTest2");
			var str = "z";
			var method = Method.New<String, int>("strstr");
			var igen = method.IlGen;

			if (str.Length == 0)
			{
				igen.Ldc(0).Ret();
			}
			else if (str.Length == 1)
			{
				var f = igen.Ldarg(N._0)
					.Ldc(str[0])
					.Ldc((Int32)0)
					.Call(Type<String>.VirtualMethod<char,Int32,int>("IndexOf"))
					.Ret();
			}
			else
			{

			}

			Console.WriteLine(method.Invoke("asdfsdzsdf"));
			Console.WriteLine(method.Invoke("asdfsdsdf"));
		}

		unsafe static int xx(object[] a, object[] b, int i)
		{
			fixed (byte* ra = a[i] as byte[], rb = b[i] as byte[])
			{
				return *((int*)ra) - *((int*)rb);
			}
		}

		static void Main(string[] args)
		{
			Test1();
			Test2();
			Test3();
			//Expression<Func<int, bool>> exprLambda = x => (x & 1) == 0;
			//var res = exprLambda.Compile();
			//var code = res.Method.GetMethodBody().GetILAsByteArray();
			//g.For(
			//    loopVariable, // если не указать, то создаст свою. начальное значение на стеке
			//    x => x < N,   // условие выхода
			//    x => x + 1,   // инкремент
			//    delegate(Local loopVariable)
			//    {
			//        var g1 = g.getBodyGen() // чтобы в аргументе не прописывать полный тип g
			//            .Ldc()
			//            .Add() ...

			//        return g1;
			//    }
			//);
		}


		//// в аргументе функции явно прописывается, чего мы ожидаем от стека и будем ли использовать аргументы
		//// на выходе придётся явно прописать, что получилось.
		//// чтобы не писать длинные выходные стеки, можно использовать continuation passing style
		//static void X<S, A>(IlGen<Lst<String,S>, Lst<A,Nil>> g)
		//{
		//    var f = Type<String>.Method<String,int>("ToString");
		//    g.Call(f).Ldarg(N._0);
		//}
	}
}
